﻿using System.Collections.Generic;

using Vestras.StarCraft2.Luna.Parser;
using Vestras.StarCraft2.Luna.Parser.Ast;

namespace Vestras.StarCraft2.Luna.Validation {
    public sealed class FunctionLevelExpressionTypeValidator : ITypeValidator {
        public System.Type[] Type {
            get {
                return new[] {
                    typeof(AddExpression),
                    typeof(AssignExpression),
                    typeof(CompareExpression),
                    typeof(ConditionExpression),
                    typeof(EquateExpression),
                    typeof(IdentifierExpression),
                    typeof(MemberAccessExpression),
                    typeof(MultiplyExpression),
                    typeof(PointerExpression),
                    typeof(ShiftExpression),
                    typeof(UnaryExpression),
                    typeof(ValueExpression),
                };
            }
        }

        public bool Validate(object value) {
            if (value != null && value is Expression) {
                Expression expression = value as Expression;
                Function parent = expression.GetLogicalParentOfType<Function>();
                // Expression types that are valid in functions
                if (expression is AssignExpression) {
                    AssignExpression assignExpression = expression as AssignExpression;
                    IList<Entity> list = new List<Entity>(ScopeEntityFilterer.FilterEntities(assignExpression.Left is MemberAccessExpression ? ((MemberAccessExpression)assignExpression.Left).GetReferencedEntities() : assignExpression.Left is IdentifierExpression ? ((IdentifierExpression)assignExpression.Left).GetReferencedEntities() : assignExpression.Left is PointerExpression ? ((PointerExpression)assignExpression.Left).GetReferencedEntities() : new List<Entity>(), assignExpression));
                    if (list.Count == 0) {
                        ErrorSink.AddError(ErrorSink.Error.Create("Cannot find entity '" + assignExpression.Left.GetQualifiedId() + "'", assignExpression));
                        return false;
                    }

                    string ambiguityEntities;
                    if (list.IsAmbiguous(out ambiguityEntities)) {
                        ErrorSink.AddError(ErrorSink.Error.Create("Ambiguity between entities " + ambiguityEntities, assignExpression));
                        return false;
                    }

                    Entity entity = list[0];
                    TypeReference type = null;
                    if (entity is Function) {
                        Function function = ((Function)entity);
                        type = function.Type;
                        if (function.Parent == null) {
                            if (function.FileName == parent.FileName) {
                                if (function.OrderIndex > parent.OrderIndex) {
                                    ErrorSink.AddError(ErrorSink.Error.Create("Cannot access function '" + function.Name + "' because it has not been declared yet", expression));
                                    return false;
                                }
                            } else {
                                if (!function.FileName.IsIncludedByFile(parent.FileName)) {
                                    ErrorSink.AddError(ErrorSink.Error.Create("Cannot find function '" + function.Name + "'", expression));
                                }
                            }
                        }
                    } else if (entity is Variable) {
                        Variable variable = ((Variable)entity);
                        type = variable.Type;
                        if (variable.Parent == null) {
                            if (variable.FileName == parent.FileName) {
                                if (variable.OrderIndex > parent.OrderIndex) {
                                    ErrorSink.AddError(ErrorSink.Error.Create("Cannot assign a value to variable '" + variable.Name + "' because it has not been declared yet", expression));
                                    return false;
                                }
                            } else {
                                if (!variable.FileName.IsIncludedByFile(parent.FileName)) {
                                    ErrorSink.AddError(ErrorSink.Error.Create("Cannot find variable '" + variable.Name + "'", expression));
                                }
                            }
                        }

                        variable.HasBeenAssigned = true;
                    } else if (entity is Typedef) {
                        Typedef t = (Typedef)entity;
                        type = new TypeReference(t.CreatedType);
                        if (t.Parent == null) {
                            if (t.FileName == parent.FileName) {
                                if (t.OrderIndex > parent.OrderIndex) {
                                    ErrorSink.AddError(ErrorSink.Error.Create("Cannot access typedef '" + t.CreatedType.GetQualifiedId() + "' because it has not been declared yet", expression));
                                    return false;
                                }
                            } else {
                                if (!t.FileName.IsIncludedByFile(parent.FileName)) {
                                    ErrorSink.AddError(ErrorSink.Error.Create("Cannot find typedef '" + t.CreatedType.GetQualifiedId() + "'", expression));
                                }
                            }
                        }
                    } else if (entity is Struct) {
                        Struct s = (Struct)entity;
                        type = new TypeReference(new IdentifierExpression(((Struct)entity).Name));
                        if (s.Parent == null) {
                            if (s.FileName == parent.FileName) {
                                if (s.OrderIndex > parent.OrderIndex) {
                                    ErrorSink.AddError(ErrorSink.Error.Create("Cannot access struct '" + s.Name + "' because it has not been declared yet", expression));
                                    return false;
                                }
                            } else {
                                if (s.FileName != null && !s.FileName.IsIncludedByFile(parent.FileName)) {
                                    ErrorSink.AddError(ErrorSink.Error.Create("Cannot find s '" + s.Name + "'", expression));
                                }
                            }
                        }
                    }

                    Typedef typedef = null;
                    if (type.IsArray(out typedef)) {
                        TypeReference typeWithoutArrays = new TypeReference(typedef == null ? type.Type : typedef.OriginalType.Type);
                        if (!assignExpression.Right.DoesExpressionResolveToType(typeWithoutArrays) && !assignExpression.Right.IsNull()) {
                            ErrorSink.AddError(ErrorSink.Error.Create("Cannot convert type '" + assignExpression.Right.GetTypeQualifiedId() + "' to type '" + assignExpression.Left.GetTypeQualifiedId() + "'", assignExpression));
                            return false;
                        }
                    } else {
                        if (!assignExpression.Right.DoesExpressionResolveToType(type) && !assignExpression.Right.IsNull()) {
                            ErrorSink.AddError(ErrorSink.Error.Create("Cannot convert type '" + assignExpression.Right.GetTypeQualifiedId() + "' to type '" + assignExpression.Left.GetTypeQualifiedId() + "'", assignExpression));
                            return false;
                        }
                    }

                    IList<Entity> entities = new List<Entity>(type.GetReferencedTypes());
                    if (entities.Count == 0) {
                        ErrorSink.AddError(ErrorSink.Error.Create("Cannot find type '" + type.GetQualifiedId() + "'", type));
                        return false;
                    }

                    if (list.IsAmbiguous(out ambiguityEntities)) {
                        ErrorSink.AddError(ErrorSink.Error.Create("Ambiguity between entities " + ambiguityEntities, assignExpression));
                        return false;
                    }

                    Struct sx = entities[0] as Struct;
                    if (sx != null) {
                        if (sx.Children.Count > 1 && (assignExpression.Right is MemberAccessExpression || assignExpression.Right is PointerExpression || assignExpression.Right is IdentifierExpression)) {
                            ErrorSink.AddError(ErrorSink.Error.Create("Bulk copy is not supported.", assignExpression));
                            return false;
                        }
                    }

                    return true;
                } else if (expression is MemberAccessExpression) {
                    MemberAccessExpression memberAccessExpression = expression as MemberAccessExpression;
                    if (memberAccessExpression.Type == MemberAccessExpression.ExpressionType.FunctionCall) {
                        IList<Entity> list = new List<Entity>(memberAccessExpression.GetReferencedEntities());
                        if (list.Count == 0) {
                            ErrorSink.AddError(ErrorSink.Error.Create("Cannot find function '" + memberAccessExpression.Member.GetQualifiedId() + "'", memberAccessExpression));
                            return false;
                        }

                        string ambiguityEntities;
                        if (list.IsAmbiguous(out ambiguityEntities)) {
                            ErrorSink.AddError(ErrorSink.Error.Create("Ambiguity between entities " + ambiguityEntities, memberAccessExpression));
                            return false;
                        }

                        Entity entity = list[0];
                        if (!(entity is Function)) {
                            ErrorSink.AddError(ErrorSink.Error.Create("Cannot find function '" + memberAccessExpression.Member.GetQualifiedId() + "'", memberAccessExpression));
                            return false;
                        }

                        Function function = entity as Function;
                        if (memberAccessExpression.ParameterExpressions.Count != function.Parameters.Count) {
                            ErrorSink.AddError(ErrorSink.Error.Create("Cannot find function '" + memberAccessExpression.Member.GetQualifiedId() + "' with argument count '" + memberAccessExpression.ParameterExpressions.Count.ToString() + "'", memberAccessExpression));
                            return false;
                        }

                        if (function.Parent == null) {
                            if (function.FileName == parent.FileName) {
                                bool prototypeValid = function.Prototype != null && function.Prototype.OrderIndex < parent.OrderIndex;
                                if (!prototypeValid) {
                                    if (function.OrderIndex > parent.OrderIndex) {
                                        ErrorSink.AddError(ErrorSink.Error.Create("Cannot access function '" + function.Name + "' because it has not been declared yet", expression));
                                        return false;
                                    }
                                }
                            } else {
                                if (!function.FileName.IsIncludedByFile(parent.FileName)) {
                                    ErrorSink.AddError(ErrorSink.Error.Create("Cannot find function '" + function.Name + "'", expression));
                                }
                            }
                        }

                        int index = 0;
                        foreach (Variable param in function.Parameters) {
                            Expression paramExpression = memberAccessExpression.ParameterExpressions[index];
                            if (!paramExpression.DoesExpressionResolveToType(param.Type) && !paramExpression.IsNull()) {
                                string typeId = paramExpression.GetTypeQualifiedId();
                                ErrorSink.AddError(ErrorSink.Error.Create(typeId == "" ? ("Cannot find entity '" + paramExpression.GetQualifiedId() + "'") : ("Cannot convert type '" + typeId + "' to type '" + param.Type.GetQualifiedId() + "'"), paramExpression));
                                return false;
                            }

                            index++;
                        }

                        return true;
                    }
                }

                // Expression types that are invalid in functions
                if (!(expression.Parent is Expression)) {
                    ErrorSink.AddError(ErrorSink.Error.Create("An expression of type '" + expression.GetType().Name + "' cannot be a top level expression.", expression));
                    return false;
                }

                return true;
            }

            return false;
        }
    }
}
